/*
 *  main.cpp
 *  Ray Tracer
 *
 *  Created by Chirantan Ekbote on 10/5/10.
 *  Copyright 2010 Ohio State University. All rights reserved.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <iostream.h>
#include <math.h>
#include <vector.h>

#include <Inventor/SbLinear.h>
#include <Inventor/actions/SoGetMatrixAction.h>
#include <Inventor/nodes/SoPerspectiveCamera.h>
#include <Inventor/nodes/SoTransform.h>
#include <Inventor/nodes/SoSphere.h>
#include <Inventor/nodes/SoCube.h>
#include <Inventor/nodes/SoLight.h>
#include <Inventor/nodes/SoPointLight.h>

#include "../include/rayTracer.h"
#include "../include/OSUInventor.h"

void usage_error()
{
	cerr << "Usage: rt <input.iv> <output.ppm> <xres> <yres> <shadow_on> <reflection_on> " <<
	"<refraction_on> <include_triangles> <use_textures>" << endl;
	exit(EXIT_FAILURE);
}

int main(int argc, char **argv) 
{
	if (argc != 10) { usage_error(); }
	
	SoDB::init();
	OSUInventorScene *scene = new OSUInventorScene(argv[1]);
	
	cout << "Read objects from file " << argv[1] << "." << endl;
	cout << "Number of objects = " 
	<< scene->Objects.getLength() << "." << endl;
	
	// create the image
	image im = imageInit(atoi(argv[3]), atoi(argv[4]));
	
	// get the parameters
	int numSuperSamples = (atoi(argv[5]) == 0) ? 1 : NUM_SUPERSAMPLES;
	int dof = (atoi(argv[7]) == 0) ? 1 : atoi(argv[7]);
	int numCameras = (atoi(argv[7]) == 0) ? 1 : NUM_CAMERAS;
	float cameraJitter = (numCameras == 1) ? 0.0f : CAMERA_JITTER;
	param p = {numSuperSamples, atoi(argv[6]), dof, numCameras, cameraJitter, atoi(argv[9])};

	 
	
	// create the list of objects
	object* shapes;
	if (atoi(argv[8])) { // Additional triangle
		shapes = (object*) malloc((scene->Objects.getLength()+2) * sizeof(object));
		shapes[0].shape.len = scene->Objects.getLength() + 1;
	}else {
		shapes = (object*) malloc((scene->Objects.getLength()+1) * sizeof(object));
		shapes[0].shape.len = scene->Objects.getLength();
	}
	
	shapes[0].type = LENGTH;
	
	// add objects to list
	for (int i = 0; i < scene->Objects.getLength(); i++) {
		OSUObjectData * obj = (OSUObjectData *)scene->Objects[i];
		
		if (!obj->Check()) {
			cerr << "Error detected in OSUObjectData for object " << i << "." 
			<< endl;
			exit(20);
		};
		
		SoType shape_type = obj->shape->getTypeId();
		
		cout << "Object " << i << " is a "
		<< shape_type.getName().getString() << "." << endl;
		
		// object transformation
		SoTransform * transformation = obj->transformation;
		SbVec3f scale_vector = transformation->scaleFactor.getValue();
		SbRotation rotation = transformation->rotation.getValue();
		SbVec3f rotation_axis;
		float rotation_angle;
		rotation.getValue(rotation_axis, rotation_angle);
		SbVec3f translation_vector = transformation->translation.getValue();
		
		// Get world coordinates
		SbMatrix T, S, R, F;
		T.setTranslate(translation_vector);
		R.setRotate(rotation);
		S.setScale(scale_vector);
		F = R*S*T;
		SbVec3f pos(0, 0, 0);
		F.multVecMatrix(pos, pos);
		
		// Print info
		cout << "  Scale by (" 
		<< scale_vector[0] << ","
		<< scale_vector[1] << ","
		<< scale_vector[2] << ")." << endl;
		cout << "  Rotate around axis (" 
		<< rotation_axis[0] << ","
		<< rotation_axis[1] << ","
		<< rotation_axis[2] << ")" 
		<< " by " << rotation_angle << " radians." << endl;
		cout << "  Translate by (" 
		<< translation_vector[0] << ","
		<< translation_vector[1] << ","
		<< translation_vector[2] << ")." << endl;
		cout << "  Object position (" 
		<< pos[0] << ","
		<< pos[1] << ","
		<< pos[2] << ")." << endl;
		
		// object material (color)
		SoMaterial * material = obj->material;
		// material->diffuseColor[0] is the first entry in an array of colors
		cout << "  Diffuse color (red, green, blue) = ("
		<< material->diffuseColor[0][0] << ","
		<< material->diffuseColor[0][1] << ","
		<< material->diffuseColor[0][2] << ")." << endl;
		
		if (shape_type == SoSphere::getClassTypeId()) {
			SoSphere * sp = (SoSphere *) obj->shape;
			
			shapes[i+1].type = SPHERE;	
			shapes[i+1].shape.sp = makeSphere(makeVec3(pos[0], pos[1], pos[2]),
											  makeRGB(material->ambientColor[0][0], 
													  material->ambientColor[0][1], 
													  material->ambientColor[0][2]),
											  makeRGB(material->diffuseColor[0][0], 
													  material->diffuseColor[0][1], 
													  material->diffuseColor[0][2]),
											  material->shininess.getValues(0)[0],
											  material->transparency.getValues(0)[0],
											  sp->radius.getValue(),
											  AIR_REFRACT_INDEX / SP_REFRACT_INDEX,
											  0);
		};
		
		if (shape_type == SoCube::getClassTypeId()) {
			SoCube * cu = (SoCube *) obj->shape;
			cout << "  Cube width, height, depth = (" 
			<< cu->width.getValue() << ","
			<< cu->height.getValue() << ","
			<< cu->depth.getValue() << ")." << endl;
			
			// Make cube
			shapes[i+1].type = CUBE;	
			shapes[i+1].shape.cu = makeCube(makeVec3(pos[0], pos[1], pos[2]), 
											cu->width.getValue() * scale_vector[0],
											cu->height.getValue() * scale_vector[1],
											cu->depth.getValue() * scale_vector[2],
											makeRGB(material->ambientColor[0][0], 
													material->ambientColor[0][1], 
													material->ambientColor[0][2]),
											makeRGB(material->diffuseColor[0][0], 
													material->diffuseColor[0][1], 
													material->diffuseColor[0][2]),
											material->shininess.getValues(0)[0],
											material->transparency.getValues(0)[0]);
		};
	};
	
	// create the list of lights
	light* lights = (light*) malloc((scene->Lights.getLength()+1) * sizeof(light));
	lights[0].type = LENGTH;
	lights[0].len = scene->Lights.getLength();
	
	// add lights to the list
	cout << endl;
	for (int j = 0; j < scene->Lights.getLength(); j++) {
		SoLight * light = (SoLight *) scene->Lights[j];
		SoType light_type = light->getTypeId();
		
		if (light->on.getValue() == true)
			cout << "Light " << j << " is on." << endl;
		else
			cout << "Light " << j << " is off." << endl;
		
		if (light_type == SoPointLight::getClassTypeId()) {
			SoPointLight * point_light = (SoPointLight *) light;
			SbVec3f location = point_light->location.getValue();
			SbVec3f color = point_light->color.getValue();
			cout << "  Point light location = (" 
			<< location[0] << ","
			<< location[1] << ","
			<< location[2] << ")." << endl;
			lights[j+1].type = POINT;
			lights[j+1].pos = makeVec3(location[0], location[1], location[2]);
			lights[j+1].color = makeRGB(color[0], color[1], color[2]);
			lights[j+1].intensity = point_light->intensity.getValue();
		};
	};
	
	// get camera
	camera cam;
	if (scene->Camera == NULL) {
		cerr << endl;
		cerr << "No camera found." << endl;
		// create default camera
		cam = makeCamera(makeVec3(0.0f, 0.0f, 1.0f),	//pos
						 makeVec3(0.0f, 0.0f, 0.0f),	//coi
						 makeVec3(0.0f, 1.0f, 0.0f),	//up
						 1.0f, PI*0.5f, p.depthOfField, atoi(argv[3]), atoi(argv[4]));
	} else {
		SoCamera * camera = scene->Camera;
		SbVec3f camera_pos = camera->position.getValue();
		SbRotation camera_orientation = camera->orientation.getValue();
		SbVec3f camera_rotation_axis;
		float camera_rotation_angle;
		camera_orientation.getValue(camera_rotation_axis, camera_rotation_angle);
		float camera_aspect_ratio = camera->aspectRatio.getValue();
		SoType camera_type = camera->getTypeId();
		
		// calculate camera direction and camera up direction
		SbVec3f camera_dir, camera_up;
		camera_orientation.multVec(SbVec3f(0, 0, -1), camera_dir);
		camera_orientation.multVec(SbVec3f(0, 1, 0), camera_up);
		
		cout << endl;
		cout << "Camera position = ("
		<< camera_pos[0] << ","
		<< camera_pos[1] << ","
		<< camera_pos[2] << ")." << endl;
		cout << "Camera rotation axis = (" 
		<< camera_rotation_axis[0] << ","
		<< camera_rotation_axis[1] << ","
		<< camera_rotation_axis[2] << ")." << endl;
		cout << "Camera rotation angle = " << camera_rotation_angle << " radians."
		<< endl;
		cout << "Camera direction = (" 
		<< camera_dir[0] << ","
		<< camera_dir[1] << ","
		<< camera_dir[2] << ")." << endl;
		cout << "Camera up direction = ("
		<< camera_up[0] << ","
		<< camera_up[1] << ","
		<< camera_up[2] << ")." << endl;
		cout << "Camera width/height aspect ratio = "
		<< camera_aspect_ratio << "." << endl;
		
		// camera MUST be a perspective camera
		if (camera_type != SoPerspectiveCamera::getClassTypeId()) {
			cerr << "Camera is not a perspective camera." << endl;
			exit(EXIT_FAILURE);
		};
		SoPerspectiveCamera * perspective_camera = (SoPerspectiveCamera *) camera;
		float camera_height_angle = perspective_camera->heightAngle.getValue();
		cout << "Perspective camera height angle = "
		<< camera_height_angle << " radians." << endl;
		
		// create camera
		cam = makeCamera(makeVec3(camera_pos[0], camera_pos[1], camera_pos[2]),	//pos
						 makeVec3(camera_pos[0] + camera_dir[0],				//coi
								  camera_pos[1] + camera_dir[1], 
								  camera_pos[2] + camera_dir[2]), 
						 makeVec3(camera_up[0], camera_up[1], camera_up[2]),	//up
						 camera_aspect_ratio, camera_height_angle, p.depthOfField, 
						 atoi(argv[3]), atoi(argv[4]));
	};
	
	if (atoi(argv[8])) { // Add triangles
		shapes[shapes[0].shape.len].type = TRIANGLE;
		vec3 v1 = vec3Add(cam.pos, vec3Mul(cam.n, -10.0f));
		shapes[shapes[0].shape.len].shape.t = makeTriangle(v1,
														   vec3Add(v1, vec3Mul(cam.u, 3.0f)),
														   vec3Add(v1, vec3Mul(cam.v, 3.0f)), 
														   makeRGB(0.3f, 0.3f, 0.3f), 
														   makeRGB(0.7f, 0.1f, 0.2f), 
														   0.0f, 0.0f);
		
	}
	
	rt(shapes, lights, &cam, &im, &p);	// Do ray tracing
	imageWritePPM(&im, argv[2]);	// Write output image
	free(shapes);					// Free up memory
	free(lights);
	
	return(EXIT_SUCCESS);
	
}

